﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace CommLib
{
    interface ICommCallback
    {
        void onConnectionClosed();
        void onMessageSms(MessageSms msg);
        void onMessageBattery(MessageBattery msg);
        void onMessageSignal(MessageSignal msg);
        void onMessageContacts(MessageContacts msg);
        void onMessageSmsList(MessageSmsList msg);
        void onMessageConversations(MessageConversations msg);
        void onMessageConversation(MessageConversation msg);
        void onMessageAlive();
        void onMessageSmsSent(int statusCode);
        void onMessageSmsDelivered(int statusCode);
        void onMessageSmsReceived();
        void onMessageCalendar(MessageCalendar msg);
        void onMessageCalendars(MessageCalendars msg);
        void onMessageCallLog(MessageCallLog msg);
    }

    class CommThread
    {
        private readonly static int ALIVE_INTERVAL = 5000;

        private volatile bool mStop;
        private volatile bool mIsRunning = false;
        private volatile Queue<BluetoothMessage> mMessages;

        private readonly ICommCallback mCallback;
        private readonly NetworkStream mStream;
        private readonly byte[] mReadBuffer;

        private int mLastSendTime;
        private int mRead;
        private byte[] mBuffer;

        public CommThread(NetworkStream stream, ICommCallback callback)
        {
            mCallback = callback;
            mStream = stream;
            mMessages = new Queue<BluetoothMessage>();
            mReadBuffer = new byte[1024];
            mBuffer = new byte[1024];
        }

        public void loop()
        {
            mStop = false;
            mIsRunning = true;
            mRead = 0;

            MessageStatus aliveMsg = new MessageStatus(StatusType.Alive);

            if (mCallback == null || mStream == null)
                mStop = true;

            while (!mStop)
            {
                // Lesen
                readMessage();

                // Schreiben
                if (mMessages.Count > 0)
                {
                    // Nachricht schreiben
                    sendMessage(mMessages.Dequeue());
                }
                else if (Environment.TickCount - mLastSendTime > ALIVE_INTERVAL)
                {
                    // Alive schreiben
                    sendMessage(aliveMsg);
                }

                // Prozessorlast reduzieren
                Thread.Sleep(1);
            }

            cleanup();

            mIsRunning = false;

            mCallback.onConnectionClosed();
        }

        public void stop()
        {
            mStop = true;
        }

        public bool isRunning()
        {
            return mIsRunning;
        }

        public void offerMessage(BluetoothMessage message)
        {
            if (message != null)
                mMessages.Enqueue(message);
        }

        private void cleanup()
        {
            if (mStream != null)
                mStream.Close();
        }

        private void sendMessage(BluetoothMessage msg)
        {
            byte[] data = msg.getBytes();

            try
            {
                mStream.Write(data, 0, data.Length);
                mLastSendTime = Environment.TickCount;
            }
            catch (IOException)
            {
                mStop = true;
            }
        }

        private void readMessage()
        {
            if (mStream.DataAvailable)
            {
                // Daten lesen
                int size = mStream.Read(mReadBuffer, 0, mReadBuffer.Length);

                if (size > 0)
                {
                    // Datenpuffer vergrößern, falls nötig
                    if (mBuffer.Length < size + mRead)
                    {
                        Array.Resize<byte>(ref mBuffer, mRead + size);
                    }

                    // Eingehende Daten anfügen
                    Array.Copy(mReadBuffer, 0, mBuffer, mRead, size);
                    mRead += size;

                    // Prüfen, ob komplette Nachrichten vorhanden
                    bool nextMsg = false;

                    do
                    {
                        nextMsg = false;

                        // Erste 4 Bytes sind Länge der Nachricht
                        if (mRead > 4)
                        {
                            MemoryStream ms = new MemoryStream(mBuffer, false);
                            BinaryReader br = new BinaryReader(ms, Encoding.UTF8);
                            int length = br.ReadInt32();

                            // BinaryReader liest LittleEndian, es kommt aber BigEndian rein
                            length = BluetoothMessage.switchEndianness(length);

                            if (mRead >= length)
                            {
                                // Länge passt, komplette Nachricht erhalten
                                processMessage(length, br);

                                // Nachricht aus Puffer entfernen
                                mRead -= (length + 4);
                                byte[] oldBuffer = mBuffer;
                                mBuffer = new byte[mRead];
                                Array.Copy(oldBuffer, length + 4, mBuffer, 0, mRead);

                                nextMsg = true;
                            }
                        }
                    } while (nextMsg);
                }
            }
        }

        private void processMessage(int length, BinaryReader br)
        {
            byte type = br.ReadByte();

            if (type >= 0)
            {
                // Typ ist gültig
                // Nachricht verarbeiten
                switch ((BtMessageType)type)
                {
                    case BtMessageType.SMS:
                        mCallback.onMessageSms(new MessageSms(mBuffer, length));
                        break;

                    case BtMessageType.Battery:
                        mCallback.onMessageBattery(new MessageBattery(mBuffer, length));
                        break;

                    case BtMessageType.Signal:
                        mCallback.onMessageSignal(new MessageSignal(mBuffer, length));
                        break;

                    case BtMessageType.Status:
                        processStatusMsg(new MessageStatus(mBuffer, length));
                        break;

                    case BtMessageType.Contacts:
                        mCallback.onMessageContacts(new MessageContacts(
                                mBuffer, length));
                        break;

                    case BtMessageType.SmsList:
                        mCallback.onMessageSmsList(new MessageSmsList(mBuffer,
                                length));
                        break;

                    case BtMessageType.ThreadList:
                       mCallback.onMessageConversations(new MessageConversations(mBuffer, length));
                        break;

                    case BtMessageType.Conversation:
                        mCallback.onMessageConversation(new MessageConversation(mBuffer, length));
                        break;

                    case BtMessageType.Request:
                        // Nichts zu tun
                        break;

                    case BtMessageType.Calendar:
                        mCallback.onMessageCalendar(new MessageCalendar(mBuffer, length));
                        break;

                    case BtMessageType.CalendarList:
                        mCallback.onMessageCalendars(new MessageCalendars(mBuffer, length));
                        break;

                    case BtMessageType.CallLog:
                        mCallback.onMessageCallLog(new MessageCallLog(mBuffer, length));
                        break;
                }
            }
        }

        private void processStatusMsg(MessageStatus msg)
        {
            switch (msg.getType())
            {
                case StatusType.Alive:
                    mCallback.onMessageAlive();
                    break;

                case StatusType.SmsSent:
                    mCallback.onMessageSmsSent(msg.getValue());
                    break;

                case StatusType.SmsDelivered:
                    mCallback.onMessageSmsDelivered(msg.getValue());
                    break;

                case StatusType.SmsReceived:
                    mCallback.onMessageSmsReceived();
                    break;

                case StatusType.Unknown:
                    // Nichts zu tun
                    break;
            }
        }

    }
}
